#include "WraithSM.h"
#include "Blackboard.h"

static const int WanderNextMoveTime = 60;
static const int WanderAggroCheckTime = 2;

//static const int BeginFleeThresholdDist = 64;
//static const int QuitFleeToAttackThresholdDist = 70;
//static const int QuitFleeToWanderThresholdDist = 160;

static const int BeginFleeThresholdDist = 96;
static const int QuitFleeToAttackThresholdDist = 150;
static const int QuitFleeToWanderThresholdDist = 256;

static const int BeginScatterThresholdDist = 32;
static const int QuitScatterToAttackThresholdDist = 96;
static const int QuitScatterToWanderThresholdDist = 192;

static const int FleeReevaluateFleeCheckTime = 1;
static const int FlankThresholdDist = 16;

WraithSM::WraithSM(BWAPI::Unit* myUnit):
wanderState(this),
idleState(this),
attackState(this),
fleeState(this),
scatterState(this)
{
	this->myUnit = myUnit;
	frameCounter = 0;
	currentState = 0;
	mainTarget = 0;
	masterAttackTile = BWAPI::TilePositions::None;
	//changeState(getWanderState());
	changeState(getIdleState());
}

WraithSM::~WraithSM()
{
	
}

void WraithSM::changeState(WraithBaseState* state)
{
	if(currentState)
		currentState->exit();
	currentState = state;
	if(currentState)
		currentState->enter();
}

void WraithSM::onFrame()
{
	if(expired())
		return;
	frameCounter++;
	if(currentState)
		currentState->onFrame();
}

bool WraithSM::expired()
{
	return !(myUnit && myUnit->exists());
}

void WraithSM::SetAttackTile(BWAPI::TilePosition attackTile)
{
	if(attackTile != masterAttackTile)
	{
		masterAttackTile = attackTile;
		getUnit()->attack(BWAPI::Position(masterAttackTile));
	}
}

// STATES

// base
std::vector<WraithSM*>& WraithBaseState::getOtherMembers()
{
	return wraithSM->getOtherMembers();
}

BWAPI::Unit* WraithBaseState::getUnit()
{
	if(wraithSM && wraithSM->getUnit() && wraithSM->getUnit()->exists())
		return wraithSM->getUnit();
	return 0;
}

BWAPI::Unit* WraithBaseState::findNearestEnemy()
{
	BWAPI::Unit* currentBest = 0;
	BWAPI::Position myPos = getUnit()->getPosition(), closePos;
	double bestDist=-1;
	for(std::set<BWAPI::Unit*>::const_iterator i=BWAPI::Broodwar->getAllUnits().begin();i!=BWAPI::Broodwar->getAllUnits().end();i++)
	{
		if(!(*i) || !(*i)->exists())
			continue;
		if ((*i)->getPlayer()->isEnemy(getUnit()->getPlayer()))
		{
			BWAPI::Position testPos = (*i)->getPosition();
			double d = testPos.getDistance(myPos);
			if(!currentBest) {
				currentBest = (*i);
				bestDist = d;
			}
			else {
				if(d<bestDist) {
					currentBest = (*i);
					bestDist = d;
				}
			}
		}
	}
	if(currentBest) {
		//BWAPI::Broodwar->sendText("Nearest enemy: %s", currentBest->getType().getName().c_str());
	}
	else {
		//BWAPI::Broodwar->sendText("No nearest enemy");
	}
	return currentBest;
}


WraithSM* WraithBaseState::findNearestMember()
{
	WraithSM* currentBest = 0;
	BWAPI::Position myPos = getUnit()->getPosition(), closePos;
	double bestDist=-1;
	for(std::vector<WraithSM*>::const_iterator i=getOtherMembers().begin();i!=getOtherMembers().end();i++)
	{
		if(!(*i) || !(*i)->getUnit()->exists())
			continue;
		BWAPI::Unit* unit = (*i)->getUnit();
		
		BWAPI::Position testPos = unit->getPosition();
		double d = testPos.getDistance(myPos);
		if(!currentBest) {
			currentBest = *i;
			bestDist = d;
		}
		else {
			if(d<bestDist) {
				currentBest = *i;
				bestDist = d;
			}
		}
	}
	if(currentBest) {
		//BWAPI::Broodwar->sendText("Nearest enemy: %s", currentBest->getType().getName().c_str());
	}
	else {
		//BWAPI::Broodwar->sendText("No nearest enemy");
	}
	return currentBest;
}

// wander
WraithIdleState::WraithIdleState(WraithSM *wk) : WraithBaseState(wk)
{
	
}

WraithIdleState::~WraithIdleState()
{
	
}

void WraithIdleState::enter()
{
	//BWAPI::Broodwar->sendText("Entering Idle");
	timeIdling = 0;
}

void WraithIdleState::onFrame()
{
	if(getUnit()->isAttacking()) {
		wraithSM->changeState(wraithSM->getAttackState());
		return;
	}
	if(!getUnit()->isMoving()) {
		timeIdling++;
		if(timeIdling > 48) {
			if(wraithSM->GetAttackTile() != BWAPI::TilePositions::None) {
				wraithSM->getUnit()->attack(BWAPI::Position(wraithSM->GetAttackTile()));
			}
		}
	}
	else
		timeIdling = 0;
}

void WraithIdleState::exit()
{

}


// wander
WraithWanderState::WraithWanderState(WraithSM *wk) : WraithBaseState(wk)
{
	
}

WraithWanderState::~WraithWanderState()
{
	
}

void WraithWanderState::enter()
{
	//BWAPI::Broodwar->sendText("Entering Wander");
}

void WraithWanderState::onFrame()
{
	if(wraithSM->getFrameCounter()%WanderNextMoveTime == 0) {
		int w=BWAPI::Broodwar->mapWidth()*32, h=BWAPI::Broodwar->mapHeight();
		getUnit()->attack(BWAPI::Position(rand()%w, rand()%h));
	}
	if(getUnit()->isAttacking()) {
		wraithSM->changeState(wraithSM->getAttackState());
		return;
	}
}

void WraithWanderState::exit()
{

}

// attack
WraithAttackState::WraithAttackState(WraithSM *wk) : WraithBaseState(wk)
{
	
}

WraithAttackState::~WraithAttackState()
{
	
}

WraithAttackState* WraithAttackState::reconstruct(BWAPI::Unit* target)
{
	forceTarget = target;
	return this;
}

void WraithAttackState::enter()
{
	//BWAPI::Broodwar->sendText("Entering Attack");
}

void WraithAttackState::onFrame()
{
	if(!getUnit()->isAttacking()) {
		wraithSM->changeState(wraithSM->getIdleState());
		return;
	}
	BWAPI::Unit* target = getUnit()->getTarget();
	if(!target || !target->exists())
		target = getUnit()->getOrderTarget();
	if(!target || !target->exists())
		target = 0;
	if(!target) {
		//wraithSM->changeState(wraithSM->getWanderState());
		wraithSM->changeState(wraithSM->getIdleState());
		return;
	}
	BWAPI::Position targPos = target->getPosition(), myPos = getUnit()->getPosition();
	BWAPI::Position dd = targPos-myPos;
	int d2 = dd.x()*dd.x()+dd.y()*dd.y();
	//BWAPI::Broodwar->sendText("Pos %d %d  %d %d  %d", myPos.x(), myPos.y(), targPos.x(), targPos.y(), d2);
	if(d2 < BeginFleeThresholdDist*BeginFleeThresholdDist) {
		wraithSM->changeState(wraithSM->getFleeState());
		return;
	}
	else if(getUnit()->getHitPoints()>20 && !getUnit()->isStimmed()) {
		getUnit()->useTech(BWAPI::TechTypes::Stim_Packs);
	}
	else
	{
		WraithSM* nearAlly = findNearestMember();
		if(nearAlly)
		{
			BWAPI::Position allyPos = nearAlly->getUnit()->getPosition();
			dd = allyPos-myPos;
			d2 = dd.x()*dd.x()+dd.y()*dd.y();
			if(d2 < BeginScatterThresholdDist*BeginScatterThresholdDist) {
				wraithSM->changeState(wraithSM->getScatterState());
				return;
			}
		}
	}
}

void WraithAttackState::exit()
{
	forceTarget = 0;
}

// flee
WraithFleeState::WraithFleeState(WraithSM *wk) : WraithBaseState(wk)
{
	
}

WraithFleeState::~WraithFleeState()
{
	
}

void WraithFleeState::enter()
{
	//BWAPI::Broodwar->sendText("Entering Flee");
}

void WraithFleeState::onFrame()
{
	BWAPI::Unit* nearest = findNearestEnemy();
	if(!nearest) {
		wraithSM->changeState(wraithSM->getIdleState());
		return;
	}
	BWAPI::Position targPos = nearest->getPosition(), myPos = getUnit()->getPosition();

	BWAPI::Position dd = targPos-myPos;
	int d2 = dd.x()*dd.x()+dd.y()*dd.y();
	if(d2 > QuitFleeToWanderThresholdDist*QuitFleeToWanderThresholdDist) {
		wraithSM->changeState(wraithSM->getIdleState());
		return;
	}
	else if(d2 > QuitFleeToAttackThresholdDist*QuitFleeToAttackThresholdDist) {
		getUnit()->attack(nearest);
		wraithSM->changeState(wraithSM->getAttackState());
		return;
	}
	else {
		if(wraithSM->getFrameCounter()%FleeReevaluateFleeCheckTime == 0) {
			int xDir = myPos.x()>targPos.x()?1:(myPos.x()==targPos.x()?0:-1);
			int yDir = myPos.y()>targPos.y()?1:(myPos.y()==targPos.y()?0:-1);
			if(xDir!=0 || yDir!=0) {
				int moveScale = 16;
				moveScale = 128;
				BWAPI::Position wantToMove(myPos.x()+xDir*moveScale, myPos.y()+yDir*moveScale);
				getUnit()->move(wantToMove);
			}
		}
	}
}

void WraithFleeState::exit()
{

}


// scatter
WraithScatterState::WraithScatterState(WraithSM *wk) : WraithBaseState(wk)
{
	
}

WraithScatterState::~WraithScatterState()
{
	
}

void WraithScatterState::enter()
{
	//BWAPI::Broodwar->sendText("Entering Scatter");
}

void WraithScatterState::onFrame()
{
	WraithSM* nearestWK = findNearestMember();
	if(!nearestWK) {
		wraithSM->changeState(wraithSM->getIdleState());
		return;
	}
	BWAPI::Unit* nearest = nearestWK->getUnit();
	BWAPI::Position targPos = nearest->getPosition(), myPos = getUnit()->getPosition();
	BWAPI::Position dd = targPos-myPos;
	int d2 = dd.x()*dd.x()+dd.y()*dd.y();
	if(d2 > QuitScatterToWanderThresholdDist*QuitScatterToWanderThresholdDist) {
		wraithSM->changeState(wraithSM->getIdleState());
		return;
	}
	else if(d2 > QuitScatterToAttackThresholdDist*QuitScatterToAttackThresholdDist) {
		getUnit()->attack(nearest);
		wraithSM->changeState(wraithSM->getAttackState());
		return;
	}
	else {
		if(wraithSM->getFrameCounter()%FleeReevaluateFleeCheckTime == 0) {
			int xDir = myPos.x()>targPos.x()?1:(myPos.x()==targPos.x()?0:-1);
			int yDir = myPos.y()>targPos.y()?1:(myPos.y()==targPos.y()?0:-1);
			if(xDir!=0 || yDir!=0) {
				int moveScale = 16;
				moveScale = 128;
				BWAPI::Position wantToMove(myPos.x()+xDir*moveScale, myPos.y()+yDir*moveScale);
				getUnit()->move(wantToMove);
			}
		}
	}
}

void WraithScatterState::exit()
{

}


// hold flank
WraithHoldFlankState::WraithHoldFlankState(WraithSM *wk) : WraithBaseState(wk)
{
	
}

WraithHoldFlankState::~WraithHoldFlankState()
{
	
}

WraithHoldFlankState* WraithHoldFlankState::reconstruct(BWAPI::Position flank)
{
	flankPos = flank;
	return this;
}

void WraithHoldFlankState::enter()
{
	//BWAPI::Broodwar->sendText("Entering HoldFlank");
	inFlankPosition = false;
}

void WraithHoldFlankState::onFrame()
{
	BWAPI::Position myPos = getUnit()->getPosition();
	BWAPI::Position dd = flankPos-myPos;
	int d2 = dd.x()*dd.x()+dd.y()*dd.y();
	if(d2 > FlankThresholdDist*FlankThresholdDist) {
		if(getUnit()->isMoving()) {
			if(getUnit()->getTargetPosition()!=flankPos) {
				getUnit()->move(flankPos);
			}
		}
		else {
			getUnit()->move(flankPos);
		}
	}
	else {
		if(getUnit()->isMoving()) {
			getUnit()->stop();
			getUnit()->holdPosition();
		}
		inFlankPosition = true;
	}
}

void WraithHoldFlankState::exit()
{

}


